/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.painting.views;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.view.animation.Transformation;
import android.widget.AbsoluteLayout;
import com.hercules.android.receiver.AndroidReceiver;
import com.hercules.android.receiver.DebugHelper;
import com.hercules.android.receiver.LogUtil;
import com.hercules.android.receiver.app.DisplayApplicationActivity;
import com.hercules.android.receiver.handlers.events.EventRaiser;
import com.hercules.android.receiver.handlers.events.KeyMappingService;
import com.hercules.android.receiver.model.commands.XRECommand;
import com.hercules.android.receiver.model.commands.XREDeleteCommand;
import com.hercules.android.receiver.model.commands.XRESetCommand;
import com.hercules.android.receiver.model.objects.XREApplication;
import com.hercules.android.receiver.model.objects.XREFlash;
import com.hercules.android.receiver.model.objects.XREHTMLText;
import com.hercules.android.receiver.model.objects.XREHTMLView;
import com.hercules.android.receiver.model.objects.XREImage;
import com.hercules.android.receiver.model.objects.XRENineSliceImage;
import com.hercules.android.receiver.model.objects.XREObject;
import com.hercules.android.receiver.model.objects.XRERectangle;
import com.hercules.android.receiver.model.objects.XREResource;
import com.hercules.android.receiver.model.objects.XREText;
import com.hercules.android.receiver.model.objects.XRETextInput;
import com.hercules.android.receiver.model.objects.XREVideo;
import com.hercules.android.receiver.model.objects.XREView;
import com.hercules.android.receiver.painting.PaintersUtil;
import com.hercules.android.receiver.painting.XREObjectPainterException;
import com.hercules.android.receiver.painting.resources.BaseXREResourcePainter;
import com.hercules.android.receiver.painting.resources.XREApplicationPainter;
import com.hercules.android.receiver.painting.resources.XREFlashPainter;
import com.hercules.android.receiver.painting.resources.XREHTMLTextPainter;
import com.hercules.android.receiver.painting.resources.XREHTMLViewPainter;
import com.hercules.android.receiver.painting.resources.XREImagePainter;
import com.hercules.android.receiver.painting.resources.XRENineSliceImagePainter;
import com.hercules.android.receiver.painting.resources.XRERectanglePainter;
import com.hercules.android.receiver.painting.resources.XRETextInputPainter;
import com.hercules.android.receiver.painting.resources.XRETextPainter;
import com.hercules.android.receiver.painting.resources.XREVideoPainter;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * This class is the UI correspondence of XREView. It extends AbsoluteLayout to allow tree hierarchy of the views. It
 * can have attached resource, so it will be painted in different ways. All resource painters should extend
 * {@link BaseXREResourcePainter}. It has some methods to be overridden that can help to display resource on screen.
 * <p>
 * AndroidAssembly1 this class was added in Android Assembly 1
 * <p>
 * <b> Thread safety: </b> This class is not thread safe since it is mutable.
 *
 * @author 7realm
 * @version 1.0
 */
@SuppressWarnings("deprecation")
public class AndroidXREView extends AbsoluteLayout {
    /**
     * Represents the {@link EventRaiser}, used to raise resource events.
     * <p>
     * Has getter and setter. Can be any value.
     */
    private static EventRaiser eventRaiser;
    
    private static KeyMappingService keyMappingService;
    /**
     * Represents android receiver.
     * <p>
     * Can be any value. Can be set via setter. Used in attachResource, handleCommand methods.
     */
    private AndroidReceiver receiver;
    /**
     * Represents the corresponding XREView.
     * <p>
     * Can be any value. Can be set via setter. Used in all public methods.
     */
    private XREView xreView;
    /**
     * Represents the attached XREResource.
     * <p>
     * Can be any value. Can be set attachResource method. Also it can be unset by detachResouce method. Used in
     * dispatchDraw method.
     */
    private BaseXREResourcePainter attachedResource;
    /**
     * Represents the logger.
     * <p>
     * Can be any value, accessed by setter.
     */
    private static Logger logger = LogUtil.getLogger(AndroidXREView.class.getSimpleName());
    /**
     * Represents painting of view.
     * <p>
     * AndroidAssembly3 added to support on/off painting.
     */
    private PaintingState paintingState = PaintingState.NOT_PAINTED;
    /**
     * Represents cached bitmap that holds last snapshot of the view.
     * <p>
     * AndroidAssembly3 added to support on/off painting.
     */
    private Bitmap cachedBitmap;
    
    private double[][] actualMatrix = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
    private double actualInternalX = 0;
    private double actualInternalY = 0;
    
    private boolean leaf = true;
    
    /**
     * Constructor of this class.
     *
     * @param context the context
     */
    public AndroidXREView(Context context) {
        super(context);

        setClipChildren(false);
        setClipToPadding(false);

        // allow to dispatch key events
        setFocusable(true);
        setFocusableInTouchMode(true);

        // support onDraw
        setWillNotDraw(false);

        // support of pre paint transformation
        setStaticTransformationsEnabled(true);

        // support for child focusing
        setAddStatesFromChildren(true);
        
        setAnimationCacheEnabled(false);
     //   setAlwaysDrawnWithCacheEnabled(true);
    }
    
    /**
     * Gets static transformation for child. XREView matrix and alpha are applied here.
     *
     * @param child the child view
     * @param t the transformation
     * @return if the child should be transformed
     */
    @Override
    protected boolean getChildStaticTransformation(View child, Transformation t) {
        if (child instanceof AndroidXREView) {
            AndroidXREView childView = (AndroidXREView) child;
            XREView childXREView = childView.xreView;
            
            // convert the view's matrix to android matrix
            int len = 3;
            float[] values = new float[len * len];
            int index = 0;
            for (int i = 0; i < len; i++) {
                for (int j = 0; j < len; j++) {
                    values[index++] = (float) childXREView.getAndroidXREView().actualMatrix[i][j];
                }
            }
            
            //if (!childXREView.isPlayingAnimation()) {
            values[2] = (float) childXREView.getAndroidXREView().actualInternalX;
            values[5] = (float) childXREView.getAndroidXREView().actualInternalY;
            //}
            
            Matrix matrix = new Matrix();
            matrix.setValues(values);

            // set transformation
            t.getMatrix().preConcat(matrix);
            t.setAlpha((float) childXREView.getAlpha());
            t.setTransformationType(Transformation.TYPE_BOTH);
            return true;
        }
        return super.getChildStaticTransformation(child, t);
    }
    
    /**
     * Overrides parent's dispatchDraw to enable mask and clipping.
     * <p>
     * AndroidAssembly2 the method of drawing changed, now both dispatchDraw and onDraw are overridden
     *
     * @param canvas the canvas to draw into
     */
    @Override
    protected void dispatchDraw(Canvas canvas) {
        if (xreView == null) {
            super.dispatchDraw(canvas);
            return;
        }
        
        LogUtil.info(logger, this, "dispatchDraw", "dispatchDraw: {0}, {1}", xreView.getId(), xreView.getParent());
        
        int width = xreView.getWidth();
        int height = xreView.getHeight();

        switch (paintingState) {
        case SWITCHING_OFF:
            // if painting is switching off then draw it for the last time
            cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            Canvas innerCanvas = new Canvas(cachedBitmap);

            // set painting to on and draw everything on cached bitmap
            paintingState = PaintingState.LAST_DRAW;
            dispatchDraw(innerCanvas);

            // after everything is drawn, switch off painting
            paintingState = PaintingState.OFF;

            // fall through to draw bitmap
        case OFF:
            // draw saved state if painting is switched off
            canvas.drawBitmap(cachedBitmap, null, new Rect(0, 0, width, height), null);
            return;
        default:
            // normal painting for ON and LAST_DRAW
            break;
        }
        
        // clip canvas
        int clipState = -1;
        if (xreView.isClip() || xreView.getId() == XREObject.ID_ROOT_VIEW) {
            clipState = canvas.save();
            canvas.clipRect(0, 0, width, height);
        }

        if (xreView.isVisible()) {
            boolean maskDrawn = false;
            XREImage xreViewMask = xreView.getMask();

            // try draw with mask
            if (xreViewMask != null) {
                try {
                    Bitmap mask = PaintersUtil.getBitmap(xreViewMask.getUrl());
                    if (mask != null) {
                        // create inner canvas
                        Bitmap innerCanvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
                        Canvas innerCanvas = new Canvas(innerCanvasBitmap);

                        super.dispatchDraw(innerCanvas);

                        // apply gradient mask
                        Paint maskPaint = new Paint();
                        maskPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
                        innerCanvas.drawBitmap(mask, null, new Rect(0, 0, width, height), maskPaint);

                        // draw inner canvas
                        canvas.drawBitmap(innerCanvasBitmap, null, new Rect(0, 0, width, height), null);

                        maskDrawn = true;
                    }
                } catch (XREObjectPainterException e) {
                    LogUtil.error(logger, this, "onDraw", e);
                }
            }

            // if mask is not loaded, draw without mask
            if (!maskDrawn) {
                super.dispatchDraw(canvas);
            }
        }

        if (clipState != -1) {
            canvas.restoreToCount(clipState);
        }
    }

    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
    	if (xreView != null) {
			final double scaleX = xreView.getMatrix()[0][0];
			final double scaleY = xreView.getMatrix()[1][1];
			final int scrollX = getScrollX();
			final int scrollY = getScrollY();
			
			location[0] *= scaleX;
			location[1] *= scaleY;
			
			dirty.left *= scaleX;
			dirty.right *= scaleX;
			dirty.top *= scaleY;
			dirty.bottom *= scaleY;
			
			dirty.offset((int) (scrollX - scrollX * scaleX), (int) (scrollY - scrollY * scaleY));
    	}
    	
    	return super.invalidateChildInParent(location, dirty);
    }
    
    /**
     * Process onDraw event. This method will apply matrix and draw resource.
     * <p>
     * AndroidAssembly2 the method of drawing changed, now both dispatchDraw and onDraw are overridden
     *
     * @param canvas the canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        if (xreView != null && xreView.isVisible()) {
            // paint resource
            if (attachedResource != null) {
                attachedResource.onDraw(canvas);
            }

            // AndroidAssembly2 debug drawing of bounds
            if (DebugHelper.drawViewBounds) {
                float border = 1;
                int width = xreView.getWidth();
                int height = xreView.getHeight();
                Paint paint = new Paint();
                paint.setStyle(Paint.Style.STROKE);
                paint.setColor(Color.GREEN);
                paint.setStrokeWidth(border);
                width -= border / 2;
                height -= border / 2;
                canvas.drawRect(border / 2, border / 2, width, height, paint);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
    	if (xreView != null) {
			final double scaleX = this.actualMatrix[0][0];
			final double scaleY = this.actualMatrix[1][1];
			
			float newX = event.getX();
			float newY = event.getY();
			
			newX /= scaleX;
			newY /= scaleY;
			
			event.setLocation(newX, newY);
    	}
    	
    	return super.dispatchTouchEvent(event);
    }
    
    /**
     * Updates view's layout and margins.
     */
    public void updateLayout() {
        updateLayout(xreView.getWidth(), xreView.getHeight());
    }

    public void updateLayer() {
    	// update actual parent
    	AndroidXREView actualParent = getActualParent();
    	if (actualParent != null) {
    		AndroidXREView oldParent = (AndroidXREView) getParent();
    		if (oldParent != null) {
    			oldParent.removeView(this);
    		}
    		
    		AndroidXREView prevView = findPreviousView();
    		if (prevView == actualParent) {
    			actualParent.addView(this, 0);
    		} else {
    			int index = actualParent.indexOfChild(prevView) + 1;
    			if (index == 0) {
    				LogUtil.warn(logger, this, "updateLayer", "The prev view not found!");
    			}
    			actualParent.addView(this, index) ;
    			
    		}
    		
    		updateActualMatrix();
    	} else {
    		if (xreView.getId() != XREObject.ID_ROOT_VIEW) {
    			LogUtil.warn(logger, this, "updateLayer", "The actual parent not found!");
    			
	    		AndroidXREView oldParent = (AndroidXREView) getParent();
	    		if (oldParent != null) {
	    			oldParent.removeView(this);
	    		}
    		}
    	}
    	
    	if (isLeaf() != xreView.isLeaf()) {
    		List<AndroidXREView> children = new ArrayList<AndroidXREView>();
    		if (isLeaf()) {
    			getActualChildrenCore(this, children);
    		} else {
    			for (int i = 0; i < getChildCount(); ++i) {
    				AndroidXREView child = (AndroidXREView) getChildAt(i);
    				if (child != null) {
    					children.add(child);
    				}
    			}
    		}
    		
    		leaf = xreView.isLeaf();
    		
    		for (AndroidXREView child : children) {
				child.updateLayer();
			}
    	}
    }
    
    private AndroidXREView getActualParent() {
    	XREView actualParent = xreView.getParent();
    	while (actualParent != null && actualParent.isLeaf()) {
    		actualParent = actualParent.getParent();
    	}
    	return (actualParent == null ? null : actualParent.getAndroidXREView());
    }
    
    private AndroidXREView findPreviousView() {
    	int index = xreView.getParent().getChildren().indexOf(xreView);
    	if (index == 0) {
    		return xreView.getParent().getAndroidXREView();
    	} else {
    		XREView prevSibling = xreView.getParent().getChildren().get(index - 1);
    		while (prevSibling.getAndroidXREView().isLeaf() && !prevSibling.getChildren().isEmpty()) {
    			prevSibling = prevSibling.getChildren().get(prevSibling.getChildren().size() - 1);
    		}
    		return prevSibling.getAndroidXREView();
    	}
    }
    
    private void getActualChildrenCore(AndroidXREView view, List<AndroidXREView> children) {
    	for (XREView child : view.getXREView().getChildren()) {
    		children.add(child.getAndroidXREView());
    		if (child.getAndroidXREView().isLeaf()) {
    			getActualChildrenCore(child.getAndroidXREView(), children);
    		}
    	}
    }

    public void updateActualMatrix() {
    	XREView curView = xreView;
    	AndroidXREView parent = (AndroidXREView) getParent();
		actualMatrix = curView.getMatrix().clone();
		actualInternalX = curView.getInternalTranslationX();
		actualInternalY = curView.getInternalTranslationY();
		if (parent != null) {
			while (curView.getParent() != null && curView.getParent() != parent.xreView) {
				curView = curView.getParent();
				actualMatrix = matrixMultiply(curView.getMatrix(), actualMatrix);
				actualInternalX += curView.getInternalTranslationX();
				actualInternalY += curView.getInternalTranslationY();
			}
		}
		
		updateLayout();
    }
    
    public void updateChildrenActualMatrix() {
    	List<XREView> children = xreView.getAllChildren();
    	for (XREView childXREView : children) {
			AndroidXREView child = childXREView.getAndroidXREView();
			child.updateActualMatrix();
		}
    }
    
    private static double[][] matrixMultiply(double[][] matrix1, double[][] matrix2) {
    	double[][] result = new double[4][4];
    	for (int i = 0; i < 4; ++i) {
    		for (int j = 0; j < 4; ++j) {
    			for (int k = 0; k < 4; ++k) {
    				result[i][j] += matrix1[i][k] * matrix2[k][j];
    			}
    		}
    	}

        return result;
    }
    
    /**
     * Updates view's layout and margins.
     *
     * @param width the width of the view
     * @param height the height of the view
     */
    public void updateLayout(int width, int height) {
    	setMinimumWidth(width + 1);
        setMinimumHeight(height + 1);
        
        LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 
        	(int) actualMatrix[0][3], (int) actualMatrix[1][3]);
        setLayoutParams(params);
    }

    /**
     * Handles XRECommand.
     *
     * @param command the command to handle
     * @throws XREObjectPainterException if error occurred while handling command
     */
    public void handleCommand(XRECommand command) throws XREObjectPainterException {
        boolean invalidateRoot = false;
        if (command instanceof XRESetCommand) {
            final XRESetCommand setCommand = (XRESetCommand) command;

            // attach or detach resource
            if (setCommand.getTargetId() == xreView.getId()) {
                if (setCommand.getProps().containsKey("resource")) {
                    if (xreView.getResource() == null) {
                        detachResource();
                    } else {
                        attachResource();
                    }
                }

                final boolean matrixChanged = setCommand.getProps().containsKey("matrix");
                final boolean dimensionChanged = setCommand.getProps().containsKey("dimensions");
                final boolean layerChanged = setCommand.getProps().containsKey("clip") || setCommand.getProps().containsKey("visible")
        			|| setCommand.getProps().containsKey("mask") || setCommand.getProps().containsKey("parent");
                
                // matrix was changed
            	if (layerChanged) {
                    xreView.getAndroidXREView().updateLayer();
                    
                    invalidateRoot = true;
            	} else if (matrixChanged) {
            		xreView.getAndroidXREView().updateActualMatrix();
            		xreView.getAndroidXREView().updateChildrenActualMatrix();
            		
            		invalidateRoot = true;
            	} else if (dimensionChanged) {
                    updateLayout();

                    // invalidate whole tree because of clipping
                    invalidateRoot = true;
            	}

                // AndroidAssembly3 added support for on/off painting
                // check if painting has changed
                if (setCommand.getProps().containsKey("painting")) {
                    // check if painting was not switched off
                    if (paintingState == PaintingState.ON) {
                        if (!xreView.isPainting()) {
                            paintingState = PaintingState.SWITCHING_OFF;
                        }
                    }

                    // check if painting was not switched on
                    if (paintingState == PaintingState.OFF || paintingState == PaintingState.NOT_PAINTED) {
                        if (xreView.isPainting()) {
                            paintingState = PaintingState.ON;
                        }
                    }
                }
                
            }
        }

        if (command instanceof XREDeleteCommand) {
            XREDeleteCommand deleteCommand = (XREDeleteCommand) command;

            // detach resource if it is deleted
            if (attachedResource != null && attachedResource.getXREResource().getId() == deleteCommand.getTargetId()) {
                detachResource();
            }
        }

        if (attachedResource != null) {
            attachedResource.handleCommand(command);
        }

        if (invalidateRoot) {
            receiver.getRootView().postInvalidate();
        } else {
            postInvalidate();
        }
    }

    /**
     * Attach the XRE view to the this android view.
     *
     * @param attachedView the view to attach
     * @throws IllegalArgumentException if attachedView is null
     * @throws XREObjectPainterException if error occurred while attaching view
     */
    @SuppressWarnings("boxing")
    public void attachView(XREView attachedView) throws XREObjectPainterException {
        String methodName = "attachView";
        LogUtil.enter(logger, this, methodName, attachedView);

        try {
            PaintersUtil.checkNull(attachedView, "attachedView");

            // set view
            xreView = attachedView;
            
            // set resource if it present
            if (attachedView.getResource() != null) {
                attachResource();
            }

            xreView.updateMeasuredExtent(this);

            xreView.setAndroidXREView(this);
            leaf = xreView.isLeaf();
            
            // update painting state if view is painting
            if (attachedView.isPainting()) {
                paintingState = PaintingState.ON;
            }

            LogUtil.info(logger, this, methodName, "Attached view {0}, parent {1}, size {2}x{3}, position {4};{5}",
                xreView.getId(), (xreView.getParent() == null ? "none" : xreView.getParent().getId()), xreView
                    .getWidth(), xreView.getHeight(), xreView.getMatrix()[0][3], xreView.getMatrix()[1][3]);
        } catch (IllegalArgumentException e) {
            throw LogUtil.error(logger, this, methodName, e);
        } catch (XREObjectPainterException e) {
            throw LogUtil.error(logger, this, methodName, e);
        }

        LogUtil.exit(logger, this, methodName);
    }

    /**
     * Updates measured size of the view.
     *
     * @param widthMeasureSpec horizontal space requirements as imposed by the parent
     * @param heightMeasureSpec vertical space requirements as imposed by the parent
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if (xreView != null) {
            xreView.updateMeasuredExtent(this);
        }
    }

    /**
     * Attach resource for painting.
     *
     * @throws XREObjectPainterException if error occurred while attaching resource
     */
    @SuppressWarnings("boxing")
    public void attachResource() throws XREObjectPainterException {
        String methodName = "attachResource";
        LogUtil.enter(logger, this, methodName);

        try {
            // get resource
            XREResource resource = xreView.getResource();
            if (resource == null) {
                throw new XREObjectPainterException("The resource that is attached is null.");
            }

            // if resource was already attached, detach it
            if (attachedResource != null) {
                detachResource();
            }

            // create new resource wrapper
            if (resource instanceof XREApplication) {
                attachedResource = new XREApplicationPainter((XREApplication) resource);
            } else if (resource instanceof XRENineSliceImage) {
                attachedResource = new XRENineSliceImagePainter((XRENineSliceImage) resource);
            } else if (resource instanceof XREImage) {
                attachedResource = new XREImagePainter((XREImage) resource);
            } else if (resource instanceof XREHTMLText) {
                attachedResource = new XREHTMLTextPainter((XREHTMLText) resource);
            } else if (resource instanceof XREText) {
                attachedResource = new XRETextPainter((XREText) resource);
            } else if (resource instanceof XRERectangle) {
                attachedResource = new XRERectanglePainter((XRERectangle) resource);
            } else if (resource instanceof XREVideo) {
                attachedResource = new XREVideoPainter((XREVideo) resource);
            } else if (resource instanceof XREFlash) {
                attachedResource = new XREFlashPainter((XREFlash) resource);
            } else if (resource instanceof XRETextInput) {
                attachedResource = new XRETextInputPainter((XRETextInput) resource);
            } else if (resource instanceof XREHTMLView) {
                attachedResource = new XREHTMLViewPainter((XREHTMLView) resource);
            } else {
                throw new XREObjectPainterException("Resource  '" + resource + "' is not supported.");
            }

            // set common fields
            attachedResource.setReceiver(receiver);
            attachedResource.setXREView(xreView);
            attachedResource.setAndroidView(this);
            attachedResource.setEventRaiser(getEventRaiser());

            // initialize resource
            attachedResource.init();

            LogUtil.info(logger, this, methodName, "Attached resource {0} of type {1} to view {2}", resource.getId(),
                resource.getClass().getSimpleName(), xreView.getId());
        } catch (XREObjectPainterException e) {
            throw LogUtil.error(logger, this, methodName, e);
        }

        LogUtil.exit(logger, this, methodName);
    }

    /**
     * Detach recourse.
     *
     * @throws XREObjectPainterException if error occurred while detaching resource
     */
    public void detachResource() throws XREObjectPainterException {
        String methodName = "detachResource";
        LogUtil.enter(logger, this, methodName);

        try {
            if (attachedResource != null) {
                // finalize resource
                attachedResource.finish();
                attachedResource = null;
            }
        } catch (XREObjectPainterException e) {
            throw LogUtil.error(logger, this, methodName, e);
        }

        LogUtil.exit(logger, this, methodName);
    }

    /**
     * Executes action on UI thread.
     *
     * @param action the action to execute
     */
    public static void runOnUI(Runnable action) {
        DisplayApplicationActivity activity = (DisplayApplicationActivity) DebugHelper.currentActivity;
        activity.runOnUiThread(action);
    }

    /**
     * When a view has focus and the user navigates away from it, the next view is searched for
     * starting from the rectangle filled in by this method.
     *
     * By default, the rectange is the {@link #getDrawingRect})of the view.  However, if your
     * view maintains some idea of internal selection, such as a cursor, or a selected row
     * or column, you should override this method and fill in a more specific rectangle.
     *
     * @param r The rectangle to fill in, in this view's coordinates.
     */
    public void getFocusedRect(Rect r) {
        r.left = r.top = r.right = r.bottom = 0;
    }
    
    /**
     * <p>
     * Setter for the android receiver.
     * </p>
     *
     * @param receiver the android receiver to set
     */
    public void setReceiver(AndroidReceiver receiver) {
        this.receiver = receiver;
    }

    /**
     * <p>
     * Protected getter for the logger.
     * </p>
     *
     * @return the logger
     */
    protected Logger getLogger() {
        return AndroidXREView.logger;
    }

    /**
     * <p>
     * Setter for the logger.
     * </p>
     *
     * @param logger the logger to set
     */
    public void setLogger(Logger logger) {
        AndroidXREView.logger = logger;
    }

    /**
     * Get resource's measured width.
     *
     * @return measured width of resource
     */
    public int getResourceMeasuredWidth() {
        if (attachedResource != null) {
            return attachedResource.getMeasuredWidth();
        }

        return getMeasuredWidth();
    }

    /**
     * Get resource's measured height.
     *
     * @return measured height of resource
     */
    public int getResourceMeasuredHeight() {
        if (attachedResource != null) {
            return attachedResource.getMeasuredHeight();
        }

        return getMeasuredHeight();
    }

    /**
     * Gets the xre view.
     */
    public XREView getXREView() {
    	return xreView;
    }
    
    /**
     * Gets the {@link EventRaiser}, used to raise resource events.
     *
     * @return the {@link EventRaiser}, used to raise resource events
     */
    public static EventRaiser getEventRaiser() {
        return eventRaiser;
    }

    /**
     * Sets the {@link EventRaiser}, used to raise resource events.
     *
     * @param eventRaiser the {@link EventRaiser}, used to raise resource events
     */
    public static void setEventRaiser(EventRaiser eventRaiser) {
        AndroidXREView.eventRaiser = eventRaiser;
    }

	/**
	 * @param keyMappingService the keyMappingService to set
	 */
	public static void setKeyMappingService(KeyMappingService keyMappingService) {
		AndroidXREView.keyMappingService = keyMappingService;
	}

	/**
	 * @return the keyMappingService
	 */
	public static KeyMappingService getKeyMappingService() {
		return keyMappingService;
	}

	/**
	 * @return the leaf
	 */
	public boolean isLeaf() {
		return leaf;
	}

	/**
     * Represents paint state of the view.
     * <p>
     * AndroidAssembly3 this class was added in Android Assembly 3.
     *
     * @author TCSASSEMBLER
     * @version 1.0
     */
    private static enum PaintingState {
        /**
         * Initial state.
         * <p>
         * Can be changed when painting is switched on for first time.
         */
        NOT_PAINTED,
        /**
         * Represents state when painting is ON and normal flow is done.
         * <p>
         * Painting can be switched off in this state.
         */
        ON,
        /**
         * Represents state when painting is OFF and cached bitmap is painted.
         * <p>
         * Painting can be switched on in this state.
         */
        OFF,
        /**
         * Represents state, when painting was ON, but SET command received that changed painting state. At this state
         * canvas is drawn for the last time to the cached bitmap.
         * <p>
         * Should be changed to painting state LAST_DRAW.
         */
        SWITCHING_OFF,
        /**
         * Represents state, when everything is rendered for the last time and after that painting is switched off.
         * <p>
         * Should be changed to painting state OFF.
         */
        LAST_DRAW
    }
}